using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline;
using System.IO;
using System.ComponentModel;
using System.Drawing.Design;

// TODO: replace these with the processor input and output types.
using TInput = System.String;
using TOutput = System.String;

namespace ContentPipelineExtension
{
    /// <summary>
    /// Model Data Slots
    /// </summary>
    public enum ModelDataSlot
    {
        /// <summary>
        /// Bounding Box Data
        /// </summary>
        BoundingBoxs,
        /// <summary>
        /// Bounding Sphere data.
        /// </summary>
        BoundingSpheres
    }

    /// <summary>
    /// This class will be instantiated by the XNA Framework Content Pipeline
    /// to apply custom processing to content data, converting an object of
    /// type TInput to TOutput. The input and output types may be the same if
    /// the processor wishes to alter data without changing its type.
    ///
    /// This should be part of a Content Pipeline Extension Library project.
    ///
    /// TODO: change the ContentProcessor attribute to specify the correct
    /// display name for this processor.
    /// </summary>
    [ContentProcessor(DisplayName = "AABB & Normals Model Processor")]
    public class MyModelProcessor : ModelProcessor
    {
        // this constant determines where we will look for the normal map in the opaque
        // data dictionary.
        public const string NormalMapKey = "NormalMap";
        public const string HeightMapKey = "HeightMap";

        List<BoundingBox> boxs = new List<BoundingBox>();
        List<BoundingSphere> spheres = new List<BoundingSphere>();

        ContentProcessorContext context;

        bool enableLogging = false;

        /// <summary>
        /// Enable Logging
        /// </summary>
        [DefaultValue(false)]
        [Description("Set to true if you want logging on"), TypeConverter(typeof(BooleanConverter)), Editor(typeof(UITypeEditor), typeof(UITypeEditor))]
        [Category("Blacksun")]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [DisplayName("Enable Logging")]
        public bool EnableLogging
        {
            get { return enableLogging; }
            set
            {
                enableLogging = value;
            }
        }


        

        /// <summary>
        /// We override this property from the base processor and force it to always
        /// return true: tangent frames are required for normal mapping, so they should
        /// not be optional.
        /// </summary>
        [Browsable(false)]
        public override bool GenerateTangentFrames
        {
            get { return true; }
            set { }
        }

        /// <summary>
        /// The user can set this value in the property grid. If it is set, the model
        /// will use this value for its normal map texture, overriding anything in the
        /// opaque data. We use the display name and description attributes to control
        /// how the property appears in the UI.
        /// </summary>
        [DisplayName("Normal Map Texture")]
        [Description("If set, this file will be used as the normal map on the model, " +
        "overriding anything found in the opaque data.")]
        [DefaultValue("")]
        public string NormalMapTexture
        {
            get { return normalMapTexture; }
            set { normalMapTexture = value; }
        }
        private string normalMapTexture;

        [DisplayName("Height Map Texture for Paralix Maping")]
        [Description("If set, this file will be used as the height map on the model for paralix maping, " +
        "overriding anything found in the opaque data.")]
        [DefaultValue("")]
        public string HeightMapTexture
        {
            get { return heightMapTexture; }
            set { heightMapTexture = value; }
        }
        private string heightMapTexture;

        [DisplayName("Texture")]
        [Description("If set, this file will be used as the defus map on the model, " +
        "overriding anything found in the opaque data.")]
        [DefaultValue("")]
        public string Texture
        {
            get { return texture; }
            set { texture = value; }
        }
        private string texture;



        String directory;
        

        public override ModelContent Process(NodeContent input, ContentProcessorContext context)
        {
            directory = Path.GetDirectoryName(input.Identity.SourceFilename);

            LookUpNormalMapAndAddToTextures(input);
            LookUpHeightMapAndAddToTextures(input);

            string modelName = input.Identity.SourceFilename.Substring(input.Identity.SourceFilename.LastIndexOf("\\") + 1);

            if (EnableLogging)
                LogWriter.WriteToLog(string.Format("Process started for {0}", modelName));

            this.context = context;

            Dictionary<string, object> ModelData = new Dictionary<string, object>();

            ModelContent baseModel = base.Process(input, context);

            GenerateData(input);

            ModelData.Add(ModelDataSlot.BoundingBoxs.ToString(), boxs);
            ModelData.Add(ModelDataSlot.BoundingSpheres.ToString(), spheres);


            baseModel.Tag = ModelData;

            if (EnableLogging)
                LogWriter.WriteToLog(string.Format("Process completed for {0}", modelName));

            return baseModel;
        }

        private void GenerateData(NodeContent node)
        {
            MeshContent mesh = node as MeshContent;

            if (mesh != null)
            {
                MeshHelper.OptimizeForCache(mesh);

                // Look up the absolute transform of the mesh.
                Matrix absoluteTransform = mesh.AbsoluteTransform;
                
                int i = 0;

                // Loop over all the pieces of geometry in the mesh.
                foreach (GeometryContent geometry in mesh.Geometry)
                {
                    Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
                    Vector3 max = new Vector3(float.MinValue, float.MinValue, float.MinValue);

                    // Loop over all the indices in this piece of geometry.
                    // Every group of three indices represents one triangle.
                    List<Vector3> thisVerts = new List<Vector3>();
                    List<int> ind = new List<int>();

                    Vector3 vertex = Vector3.Zero;

                    foreach (int index in geometry.Indices)
                    {
                        // Look up the position of this vertex.
                        vertex = Vector3.Transform(geometry.Vertices.Positions[index], absoluteTransform);

                        // Store this data.
                        min = Vector3.Min(min, vertex);
                        max = Vector3.Max(max, vertex);

                        thisVerts.Add(vertex);

                        ind.Add(i++);
                    }

                    if (EnableLogging)
                    {
                        LogWriter.WriteToLog(string.Format("BoundingBox created min = {0}, max = {1}", min, max));
                    }
                    boxs.Add(new BoundingBox(min, max));
                    spheres.Add(BoundingSphere.CreateFromBoundingBox(boxs[boxs.Count - 1]));
                }
            }

            // Recursively scan over the children of this node.
            foreach (NodeContent child in node.Children)
            {
                GenerateData(child);
            }
        }
        /// <summary>
        /// Looks into the OpaqueData property on the "mesh" object, and looks for a
        /// a string containing the path to the normal map. The normal map is added
        /// to the Textures collection for each of the mesh's materials.
        /// </summary>
        private void LookUpNormalMapAndAddToTextures(NodeContent node)
        {
            MeshContent mesh = node as MeshContent;
            if (mesh != null)
            {
                string pathToNormalMap;

                // if NormalMapTexture hasn't been set in the UI, we'll try to look up
                // the normal map using the opaque data.
                if (String.IsNullOrEmpty(NormalMapTexture))
                {
                    pathToNormalMap =
                        mesh.OpaqueData.GetValue<string>(NormalMapKey, null);
                }
                // However, if the NormalMapTexture is set, we'll use that value for
                // ever mesh in the scene.
                else
                {
                    pathToNormalMap = NormalMapTexture;
                }

                if (pathToNormalMap == null)
                {
                    throw new InvalidContentException("the normal map is missing!");
                }
                pathToNormalMap = Path.Combine(directory, pathToNormalMap);
                foreach (GeometryContent geometry in mesh.Geometry)
                {
                    geometry.Material.Textures.Add(NormalMapKey,
                        new ExternalReference<TextureContent>(pathToNormalMap));
                }
            }

            foreach (NodeContent child in node.Children)
            {
                LookUpNormalMapAndAddToTextures(child);
            }
        }

        private void LookUpHeightMapAndAddToTextures(NodeContent node)
        {
            MeshContent mesh = node as MeshContent;
            if (mesh != null)
            {
                string pathToHightMap;

                
                if (String.IsNullOrEmpty(HeightMapTexture))
                {
                    pathToHightMap =
                        mesh.OpaqueData.GetValue<string>(HeightMapKey, null);
                }
                
                else
                {
                    pathToHightMap = HeightMapTexture;
                }

                if (pathToHightMap == null)
                {
                    throw new InvalidContentException("the hight map is missing!");
                }
                pathToHightMap = Path.Combine(directory, pathToHightMap);
                foreach (GeometryContent geometry in mesh.Geometry)
                {
                    geometry.Material.Textures.Add(HeightMapKey,
                        new ExternalReference<TextureContent>(pathToHightMap));
                }
            }

            foreach (NodeContent child in node.Children)
            {
                LookUpHeightMapAndAddToTextures(child);
            }
        }


        // acceptableVertexChannelNames are the inputs that the normal map effect
        // expects.  The NormalMappingModelProcessor overrides ProcessVertexChannel
        // to remove all vertex channels which don't have one of these four
        // names.
        static IList<string> acceptableVertexChannelNames =
            new string[]
            {
                VertexChannelNames.TextureCoordinate(0),
                VertexChannelNames.Normal(0),
                VertexChannelNames.Binormal(0),
                VertexChannelNames.Tangent(0),
            };


        /// <summary>
        /// As an optimization, ProcessVertexChannel is overriden to remove data which
        /// is not used by the vertex shader.
        /// </summary>
        /// <param name="geometry">the geometry object which contains the 
        /// vertex channel</param>
        /// <param name="vertexChannelIndex">the index of the vertex channel
        /// to operate on</param>
        /// <param name="context">the context that the processor is operating
        /// under.  in most cases, this parameter isn't necessary; but could
        /// be used to log a warning that a channel had been removed.</param>
        protected override void ProcessVertexChannel(GeometryContent geometry,
            int vertexChannelIndex, ContentProcessorContext context)
        {
            String vertexChannelName =
                geometry.Vertices.Channels[vertexChannelIndex].Name;
            
            // if this vertex channel has an acceptable names, process it as normal.
            if (acceptableVertexChannelNames.Contains(vertexChannelName))
            {
                base.ProcessVertexChannel(geometry, vertexChannelIndex, context);
            }
            // otherwise, remove it from the vertex channels; it's just extra data
            // we don't need.
            else
            {
                geometry.Vertices.Channels.Remove(vertexChannelName);
            }
        }


        protected override MaterialContent ConvertMaterial(MaterialContent material,
            ContentProcessorContext context)
        {

            EffectMaterialContent normalMappingMaterial = new EffectMaterialContent();
            normalMappingMaterial.Effect = new ExternalReference<EffectContent>
                (Path.Combine(directory, "Shaders/tutorialshaider.fx"));

            OpaqueDataDictionary processorParameters = new OpaqueDataDictionary();
            processorParameters["ColorKeyColor"] = this.ColorKeyColor;
            processorParameters["ColorKeyEnabled"] = this.ColorKeyEnabled;
            processorParameters["TextureFormat"] = this.TextureFormat;
            processorParameters["GenerateMipmaps"] = this.GenerateMipmaps;
            processorParameters["ResizeTexturesToPowerOfTwo"] =
                                                        this.ResizeTexturesToPowerOfTwo;

            // copy the textures in the original material to the new normal mapping
            // material. this way the diffuse texture is preserved. The
            // PreprocessSceneHierarchy function has already added the normal map
            // texture to the Textures collection, so that will be copied as well.
            foreach (KeyValuePair<String, ExternalReference<TextureContent>> texture
                in material.Textures)
            {
                normalMappingMaterial.Textures.Add(texture.Key, texture.Value);
            }

            // and convert the material using the NormalMappingMaterialProcessor,
            // who has something special in store for the normal map.
            return context.Convert<MaterialContent, MaterialContent>
                (normalMappingMaterial, typeof(NormalMappingMaterialProcessor).Name,
                processorParameters);
        }
    }
}